package no.uib.hplims.views.peptides;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import no.uib.hplims.Controller;
import no.uib.hplims.MyVaadinApplication;
import no.uib.hplims.models.BlastInfo;
import no.uib.hplims.models.Peptide;
import no.uib.hplims.models.PeptideFactory;
import no.uib.hplims.models.peptideinformation.PeptideInformation;
import no.uib.hplims.models.peptideinformation.Transition;
import no.uib.hplims.tools.Icons;
import no.uib.hplims.tools.events.UpdatePeptideInformationEvent;
import no.uib.hplims.tools.events.UpdatePeptideInformationListener;
import no.uib.hplims.views.orders.OrderEditor;

import org.vaadin.appfoundation.persistence.facade.FacadeFactory;

import uk.ac.ebi.kraken.interfaces.uniprot.features.Feature;
import uk.ac.ebi.kraken.interfaces.uniprot.features.FeatureSequence;
import uk.ac.ebi.kraken.model.uniprot.features.ModResFeatureImpl;
import uk.ac.ebi.kraken.model.uniprot.features.SiteFeatureImpl;
import uk.ac.ebi.kraken.model.uniprot.features.VariantFeatureImpl;

import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.util.BeanItemContainer;
import com.vaadin.terminal.ExternalResource;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Link;
import com.vaadin.ui.Table;
import com.vaadin.ui.Table.ColumnGenerator;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.Runo;

public class GeneralPeptideInformationView extends VerticalLayout {

	private static final long serialVersionUID = -8325182293170135123L;

	// Create the listener list
	protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

	private Controller controller = null;

	private Peptide peptide = null;
	private Table blastHitTable = null;
	private Table modificationsTable = null;
	private BeanItemContainer<Feature> modificationsContainer = null;

	private Button saveButton = null;
	private Button resetButton = null;
	private Button reBlastButton = null;

	private final ArrayList<Object> blastHitsVisibleColumnIds = new ArrayList<Object>();
	private final ArrayList<String> blastHitsVisibleColumnLabels = new ArrayList<String>();

	private final ArrayList<Object> modificationsVisibleColumnIds = new ArrayList<Object>();
	private final ArrayList<String> modificationsVisibleColumnLabels = new ArrayList<String>();

	public GeneralPeptideInformationView(Peptide p) {
		setCaption("General");
		setMargin(true, true, true, true);
		setSpacing(true);

		this.controller = MyVaadinApplication.getInstance().getController();
		this.peptide = p;

		init();
	}

	private void init() {
		int numblasthits = peptide.getBlastHits().size();

		addComponent(peptideInformationLayout());

		if (numblasthits > 0) {
			// There were BLAST hits for this peptide
			addComponent(getBlastHitTable());
			addComponent(getModificationsTable());

			BlastInfo selectedProtein = peptide.getSelectedProtein();
			if (selectedProtein != null) {
				updateModificationsTable(selectedProtein
						.getKnownModifications());
			} else {
				updateModificationsTable(new ArrayList<Feature>());
			}

			addComponent(saveAndCancelButtons());
		} else {
			// No BLAST hits
			addComponent(new Label("No BLAST hits returned for this peptide. Press ReBLAST to perform a new BLAST search with higher threshold."));
			addComponent(closeButton());
		}

	}

	private Component peptideInformationLayout() {
		VerticalLayout layout = new VerticalLayout();

		GridLayout grid = new GridLayout(3, 4);
		grid.setSpacing(true);
		grid.setSizeFull();

		// Row 1, Pos 1 (top left)
		Label seq = new Label("<strong>Sequence:</strong> "
				+ peptide.getPeptideSequence());
		seq.setContentMode(Label.CONTENT_XHTML);
		grid.addComponent(seq, 0, 0);

		// Row 1, Pos 2 (top middle)
		Label seqLength = new Label("<strong>Sequence Length:</strong> "
				+ peptide.getSequenceLength());
		seqLength.setContentMode(Label.CONTENT_XHTML);
		grid.addComponent(seqLength, 1, 0);

		// Row 1, Pos 3 (top right)
		Label workingTransitions = new Label();
		int numWorking = getNumberOfWorkingTransitions();
		workingTransitions.setValue(numWorking + " working transitions");
		if (numWorking > 0) {
			if (numWorking >= 3) {
				workingTransitions.setStyleName("success");

			} else {
				workingTransitions.setStyleName("warning");
			}
		} else {
			workingTransitions.setStyleName("error");
		}
		grid.addComponent(workingTransitions, 2, 0);

		// Row 2, Pos 1

		// Row 2, Pos 2

		// Row 2, Pos 3
		int numberSimilar = MyVaadinApplication
				.getInstance()
				.getController()
				.getNumberOfTimesPreviouslyOrdered(peptide.getPeptideSequence());
		Label reordered = new Label();
		reordered.setStyleName("info");
		if (numberSimilar != 1) {
			reordered
					.setValue("Ordered " + numberSimilar + " times previously");
		} else {
			reordered.setValue("Ordered " + numberSimilar + " time previously");
		}
		grid.addComponent(reordered, 2, 1);

		// Row 3, Pos 1

		// Row 3, Pos 2

		// Row 3, Pos 3

		// Row 4, Pos 1

		// Row 4, Pos 2

		// Row 4, Pos 3

		layout.addComponent(grid);

		return layout;
	}

	private Table getBlastHitTable() {
		if (blastHitTable == null) {
			// Specify which columns are visible
			blastHitsVisibleColumnIds.add("uniProtAccessionNumber");
			blastHitsVisibleColumnIds.add("proteinName");
			blastHitsVisibleColumnIds.add("proteinShortName");
			blastHitsVisibleColumnIds.add("peptideStart");
			blastHitsVisibleColumnIds.add("peptideEnd");

			// Give those columns readable names
			blastHitsVisibleColumnLabels.add("UniProt Accession #");
			blastHitsVisibleColumnLabels.add("Protein Name");
			blastHitsVisibleColumnLabels.add("Protein Short Name");
			blastHitsVisibleColumnLabels.add("Start Index");
			blastHitsVisibleColumnLabels.add("End Index");

			BeanItemContainer<BlastInfo> container = new BeanItemContainer<BlastInfo>(
					BlastInfo.class);
			container.addAll(peptide.getBlastHits());

			blastHitTable = new Table(
					"BLAST Results: (select a protein to associate with this peptide)");
			blastHitTable.setStyleName(Runo.TABLE_SMALL);
			blastHitTable.addListener(valueChangeListener);
			blastHitTable.addGeneratedColumn("uniProtAccessionNumber",
					new ColumnGenerator() {
						private static final long serialVersionUID = -8226951002615224913L;

						public Object generateCell(Table source, Object itemId,
								Object columnId) {
							BlastInfo bi = (BlastInfo) itemId;
							Link uniProtLink = new Link(
									bi.getUniProtAccessionNumber(),
									new ExternalResource(
											"http://www.uniprot.org/uniprot/"
													+ bi.getUniProtAccessionNumber()));
							// Open the URL in a new window/tab
							uniProtLink.setTargetName("_blank");
							return uniProtLink;
						}
					});

			blastHitTable.setContainerDataSource(container);
			int pageLimit = 8;
			if (pageLimit > peptide.getBlastHits().size()) {
				pageLimit = peptide.getBlastHits().size();
			}
			blastHitTable.setPageLength(pageLimit);

			blastHitTable
					.setVisibleColumns(blastHitsVisibleColumnIds.toArray());
			blastHitTable.setColumnHeaders(blastHitsVisibleColumnLabels
					.toArray(new String[0]));

			blastHitTable.setSelectable(true);
			blastHitTable.setMultiSelect(false);
			blastHitTable.setImmediate(true);
			blastHitTable.setEditable(false);
			blastHitTable.setSizeFull();
			blastHitTable.setNullSelectionItemId(null);
		}
		return blastHitTable;
	}

	private Table getModificationsTable() {
		if (modificationsTable == null) {
			modificationsTable = new Table(
					"Known modifications for current peptide");

			modificationsTable.setStyleName(Runo.TABLE_SMALL);
			modificationsTable.setSelectable(false);
			modificationsTable.setEditable(false);
			modificationsTable.setSizeFull();
			modificationsTable.setPageLength(3);

			if (modificationsContainer == null) {
				modificationsContainer = new BeanItemContainer<Feature>(
						Feature.class);
			}

			modificationsTable.setContainerDataSource(modificationsContainer);

			modificationsTable.addGeneratedColumn("positions",
					positionColumnGenerator);
			modificationsTable.addGeneratedColumn("modInfo", modInfoGenerator);

			modificationsVisibleColumnIds.add("type");
			modificationsVisibleColumnIds.add("positions");
			modificationsVisibleColumnIds.add("modInfo");

			modificationsVisibleColumnLabels.add("Type");
			modificationsVisibleColumnLabels.add("Position(s)");
			modificationsVisibleColumnLabels.add("Description");

			modificationsTable.setVisibleColumns(modificationsVisibleColumnIds
					.toArray());
			modificationsTable
					.setColumnHeaders(modificationsVisibleColumnLabels
							.toArray(new String[0]));

			// Limit width of columns to make room for modInfo
			modificationsTable.setColumnWidth("type", 70);
			modificationsTable.setColumnWidth("positions", 90);

			// Align columns
			modificationsTable.setColumnAlignment("type", Table.ALIGN_CENTER);
			modificationsTable.setColumnAlignment("positions",
					Table.ALIGN_CENTER);
		}
		return modificationsTable;
	}

	private void updateModificationsTable(List<Feature> knownMods) {
		modificationsContainer = new BeanItemContainer<Feature>(Feature.class);
		if (knownMods != null) {
			modificationsContainer.addAll(knownMods);
			getModificationsTable().setContainerDataSource(
					modificationsContainer);
			getModificationsTable().setVisibleColumns(
					modificationsVisibleColumnIds.toArray());
			getModificationsTable().setColumnHeaders(
					modificationsVisibleColumnLabels.toArray(new String[0]));
		}

	}

	private ColumnGenerator modInfoGenerator = new ColumnGenerator() {
		private static final long serialVersionUID = -9148452846992334172L;

		public Object generateCell(Table source, Object itemId, Object columnId) {
			Component description = null;

			String modDescription = "";

			if (itemId instanceof VariantFeatureImpl) {
				VariantFeatureImpl var = (VariantFeatureImpl) itemId;

				String original = "";
				String alternative = "";

				original = var.getOriginalSequence().getValue();

				if (var.getAlternativeSequences().size() > 0) {
					description = new HorizontalLayout();
					((HorizontalLayout) description).setSpacing(true);
					// DEBUG: System.out.println("Alternative sequences: " +
					// var.getAlternativeSequences().size());
					for (FeatureSequence s : var.getAlternativeSequences()) {
						alternative = s.getValue();
						modDescription = original + "->" + alternative + ";";
						Link link = new Link(
								modDescription,
								new ExternalResource(
										"http://web.expasy.org/cgi-bin/variant_pages/get-sprot-variant.pl?"
												+ var.getFeatureId().getValue()));
						link.setTargetName("_blank");
						((HorizontalLayout) description).addComponent(link);
					}
					modDescription = var.getVariantReport().getValue();
					((HorizontalLayout) description).addComponent(new Label(
							modDescription));

				} else {
					description = new Label("Missing " + modDescription);
				}
			} else if (itemId instanceof ModResFeatureImpl) {
				ModResFeatureImpl var = (ModResFeatureImpl) itemId;
				modDescription = var.getFeatureDescription().getValue();

				description = new Label(modDescription);
			} else if (itemId instanceof SiteFeatureImpl) {
				SiteFeatureImpl var = (SiteFeatureImpl) itemId;
				modDescription = var.getFeatureDescription().getValue();

				description = new Label(modDescription);
			}

			return description;
		}

	};

	private ColumnGenerator positionColumnGenerator = new ColumnGenerator() {
		private static final long serialVersionUID = -9148452846992334172L;

		public Object generateCell(Table source, Object itemId, Object columnId) {
			Label position = new Label();

			Feature f = (Feature) itemId;
			int start = f.getFeatureLocation().getStart();
			int end = f.getFeatureLocation().getEnd();

			if (start == end) {
				position.setValue("" + start);
			} else {
				position.setValue("" + start + " - " + end);
			}

			return position;
		}
	};

	private HorizontalLayout closeButton() {
		reBlastButton = new Button("ReBLAST");
		reBlastButton.addListener(clickListener);
		HorizontalLayout buttonLayout = new HorizontalLayout();
		buttonLayout.setSpacing(true);
		buttonLayout.addComponent(reBlastButton);
		return buttonLayout;
	}

	private HorizontalLayout saveAndCancelButtons() {
		saveButton = new Button("Save");
		saveButton.setIcon(Icons.saveIcon16);
		saveButton.addListener(clickListener);
		resetButton = new Button("Reset");
		resetButton.addListener(clickListener);
		HorizontalLayout buttonLayout = new HorizontalLayout();
		buttonLayout.setSpacing(true);
		buttonLayout.addComponent(saveButton);
		buttonLayout.addComponent(resetButton);
		return buttonLayout;
	}

	private ClickListener clickListener = new ClickListener() {

		private static final long serialVersionUID = 3660835856883162886L;

		public void buttonClick(ClickEvent event) {

			if (event.getButton() == saveButton) {

				BlastInfo bi = (BlastInfo) getBlastHitTable().getValue();
				if (bi == null) {
					// No protein was selected. Do nothing.
					return;
				} else {
					peptide.setPeptideName(PeptideFactory
							.generatePeptideName(bi));
					peptide.setUniProtAccession(bi.getUniProtAccessionNumber());
					peptide.setSelectedProtein(bi);
					FacadeFactory.getFacade().store(peptide);

					fireUpdatePeptideInformationEvent(new UpdatePeptideInformationEvent(
							this, peptide));
				}

			} else if (event.getButton() == resetButton) {

				if (peptide.getSelectedProtein() != null) {
					blastHitTable.select(peptide.getSelectedProtein());
				} else {
					blastHitTable
							.select(blastHitTable.getNullSelectionItemId());
				}

			} else if (event.getButton() == reBlastButton) {
				ArrayList<String> seq = new ArrayList<String>();
				seq.add(peptide.getPeptideSequence());

				controller.resubmitForBlast(seq, peptide.getBelongsToOrder(),
						peptide.getSpecies());

			}
		}
	};

	private ValueChangeListener valueChangeListener = new Property.ValueChangeListener() {

		private static final long serialVersionUID = 6659169471355402766L;

		public void valueChange(ValueChangeEvent event) {

			BlastInfo bi = (BlastInfo) event.getProperty().getValue();

			if (bi != null) {

				updateModificationsTable(bi.getKnownModifications());

			} else {

				if (peptide.getSelectedProtein() != null) {

					updateModificationsTable(peptide.getSelectedProtein()
							.getKnownModifications());

				} else {

					updateModificationsTable(new ArrayList<Feature>());

				}

			}
		}
	};

	// This methods allows classes to register for
	// UpdatePeptideInformationEvents
	public void addListener(UpdatePeptideInformationListener listener) {
		listenerList.add(UpdatePeptideInformationListener.class, listener);
	}

	// This methods allows classes to unregister for
	// UpdatePeptideInformationEvents
	public void removeListener(UpdatePeptideInformationListener listener) {
		listenerList.remove(UpdatePeptideInformationListener.class, listener);
	}

	// This private class is used to fire UpdatePeptideInformationEvents
	void fireUpdatePeptideInformationEvent(UpdatePeptideInformationEvent evt) {
		Object[] listeners = listenerList.getListenerList();
		// Each listener occupies two elements - the first is the listener class
		// and the second is the listener instance
		for (int i = 0; i < listeners.length; i += 2) {
			if (listeners[i] == UpdatePeptideInformationListener.class) {
				((UpdatePeptideInformationListener) listeners[i + 1])
						.eventOccurred(evt);
			}
		}
	}

	private int getNumberOfWorkingTransitions() {
		int working = 0;

		PeptideInformation pi = FacadeFactory.getFacade().find(
				PeptideInformation.class, peptide.getPeptideInformationID());

		for (Transition t : pi.getTransitions()) {
			if (t.getWorking().equals(Transition.WORKING)) {
				working++;
			}
		}

		return working;
	}

}
